package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.utils.NumberUtils;
import com.leyou.order.pojo.*;
import com.leyou.search.client.BrandClient;
import com.leyou.search.client.CategoryClient;
import com.leyou.search.client.GoodsClient;
import com.leyou.search.client.SpecificationClient;
import com.leyou.search.pojo.Goods;
import com.leyou.search.pojo.SearchRequest;
import com.leyou.search.pojo.SearchResult;
import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * lcd  2020/6/10
 * Description:
 */
@Service
public class SearchService {
    @Autowired
    private BrandClient brandClient;
    @Autowired
    private CategoryClient categoryClient;
    @Autowired
    private GoodsClient goodsClient;
    @Autowired
    private SpecificationClient specificationClient;
    @Autowired
    private GoodsRepository goodsRepository;
    @Autowired
    ElasticsearchTemplate template;

    public Goods buildGoods(Spu spu) {
        //1搜索字段
        Long spuId = spu.getId();
        List<Category> categories = categoryClient.queryListByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));
        if (CollectionUtils.isEmpty(categories)) {
            throw new LyException(ExceptionEnum.CATEGORY_NOT_FOUND);
        }
        List<String> categoryNames = categories.stream().map(Category::getName).collect(Collectors.toList());
        Brand brand = brandClient.queryBrandById(spu.getBrandId());
        if (brand == null) {
            throw new LyException(ExceptionEnum.BRAND_NOT_FOUND);
        }
        String all = spu.getTitle()+StringUtils.join(categoryNames," ")+brand.getName();
        //2和3查询sku
        List<Sku> skuList = goodsClient.querySkuBySpuId(spu.getId());

        if (CollectionUtils.isEmpty(skuList)) {
            throw new LyException(ExceptionEnum.GOOD_SKU_NOT_FOUND);
        }
        List<Long> priceList = new ArrayList<>();
        //3.1sku数据清洗,顺便拿到价格
        List<Map<String,Object>> skus = new ArrayList<>();
        for (Sku sku : skuList) {
            Map<String,Object> map=new HashMap<>();
            map.put("id", sku.getId());
            map.put("title", sku.getTitle());
            map.put("price", sku.getPrice());
            map.put("image", StringUtils.substringBefore(sku.getImages(),","));
            priceList.add(sku.getPrice());//2所有sku价格
            skus.add(map);//处理价格
        }

        //4可搜索的规格参数,key是规格参数,value在商品详情
        Map<String, Object> specs = new HashMap<>();
        //4.1key
        List<SpecParam> params = specificationClient.queryParamByGid(null, spu.getCid3(), true);
        if (CollectionUtils.isEmpty(params)) {
            throw new LyException(ExceptionEnum.SPEC_PARAM_NOT_FOUND);
        }
        //4.2value
        SpuDetail spuDetail = goodsClient.querySpuDetailBySpuId(spuId);
        //  又分为通用的和特有的
        Map<Long, String> genericSpec = JsonUtils.toMap(spuDetail.getGenericSpec(), Long.class, String.class);
        Map<Long, List<String>> specialSpec = JsonUtils.nativeRead(spuDetail.getSpecialSpec(), new TypeReference<Map<Long, List<String>>>() {});
        //4.3赋值
        for (SpecParam param : params) {
            String key = param.getName();
            Object value = "";
            if (param.getGeneric()) {
                value=genericSpec.get(param.getId());
                //判断是否是数字类型,如果是的话,要按照区间范围来存到elasticSearch里面,存的时候麻烦,取出来的时候方便 这个技巧有点野
                if (param.getNumeric()) {
                    value=chooseSegment(value.toString(), param);
                }
            }else {
                value = specialSpec.get(param.getId());
            }
            specs.put(key, value);
        }

        //构建goods
        Goods goods = new Goods();
        goods.setBrandId(spu.getBrandId());
        goods.setCid1(spu.getCid1());
        goods.setCid2(spu.getCid2());
        goods.setCid3(spu.getCid3());
        goods.setCreateTime(spu.getCreateTime());
        goods.setId(spu.getId());
        goods.setSubTitle(spu.getSubTitle());

        goods.setAll(all);// 1搜索字段,包含标题 分类 品牌 规格
        goods.setPrice(priceList);// 2所有sku的价格
        goods.setSkus(JsonUtils.toString(skus));// 3所有sku集合的json
        goods.setSpecs(specs);// 4所有可搜索的规格参数

        return goods;
    }

    private String chooseSegment(String value, SpecParam p) {
        double val = NumberUtils.toDouble(value);
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = NumberUtils.toDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if(segs.length == 2){
                end = NumberUtils.toDouble(segs[1]);
            }
            // 判断是否在范围内
            if(val >= begin && val < end){
                if(segs.length == 1){
                    result = segs[0] + p.getUnit() + "以上";
                }else if(begin == 0){
                    result = segs[1] + p.getUnit() + "以下";
                }else{
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    public SearchResult search(SearchRequest request) {
        Integer page = request.getPage()-1;
        Integer size = request.getSize();
        String key = request.getKey();
        //0创建查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //1结果过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","subTitle","skus"},null));
        //2分页
        queryBuilder.withPageable(PageRequest.of(page, size));
        //3过滤
        QueryBuilder basicQuery = buildBasicQuery(request);
        //3.1加上客户端传来的其他过滤条件
        queryBuilder.withQuery(basicQuery);
        //4添加分类和品牌的聚合
        String categoryAggName = "categories_agg";
        String brandAggName = "brand_agg";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));
        //5查询
        AggregatedPage<Goods> result = (AggregatedPage<Goods>)goodsRepository.search(queryBuilder.build());
        //6解析品牌和分类的聚合结果集
        List<Map<String, Object>> categories = getCategoryAgg(result.getAggregation(categoryAggName));
        List<Brand> brands=getBrandAgg(result.getAggregation(brandAggName));
        //6.1规格参数聚合
        List<Map<String, Object>> specs=null;
        if (categories != null && categories.size() == 1) {//只有是单分类的 才做聚合
            specs = buildSpecificationAgg((long)(categories.get(0).get("id")),basicQuery);
        }

        return new SearchResult( result.getContent(),result.getTotalElements(),result.getTotalPages(),categories,brands,specs);
    }

    private QueryBuilder buildBasicQuery(SearchRequest request) {
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        //查询条件
        queryBuilder.must(QueryBuilders.matchQuery("all",request.getKey()));
        //过滤条件
        Map<String, String> map = request.getFilter();
        for (Map.Entry<String, String> entry : map.entrySet()) {
            String key = entry.getKey();
            //key 分成2种, 1分类和品牌 2规格参数 1不用处理 2要处理
            if (!"cid".equals(key) &&!"brandId".equals(key) ){
                key = "specs." + key + ".keyword";
            }
            String value = entry.getValue();
            queryBuilder.filter(QueryBuilders.termQuery(key,value));
        }


        return queryBuilder;
    }

    private List<Map<String, Object>> buildSpecificationAgg(long cid, QueryBuilder basicQuery) {
        List<Map<String, Object>> specs=new ArrayList<>();
        //查询需要聚合的规格参数
        List<SpecParam> params = specificationClient.queryParamByGid(null, cid, true);
        //聚合
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(basicQuery);//基本查询条件
        for (SpecParam param : params) {
            String name = param.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs."+name+".keyword"));
        }
        //获取结果
        AggregatedPage<Goods> result = template.queryForPage(queryBuilder.build(), Goods.class);
        //解析结果
        Aggregations aggregations = result.getAggregations();
        for (SpecParam param : params) {
            String name = param.getName();
            StringTerms terms = aggregations.get(name);
            List<String> options = terms.getBuckets().stream().map(StringTerms.Bucket::getKeyAsString).collect(Collectors.toList());
            Map<String, Object> map=new HashMap<>();
            map.put("k",name);
            map.put("options",options);
            specs.add(map);
        }
        return specs;
    }

    private List<Brand> getBrandAgg(Aggregation aggregation) {
        LongTerms longTerms = (LongTerms) aggregation;
        return longTerms.getBuckets().stream().map(x -> brandClient.queryBrandById(x.getKeyAsNumber().longValue())).collect(Collectors.toList());
//        List<LongTerms.Bucket> buckets = longTerms.getBuckets();
//        List<Brand> brandList = new ArrayList<>();
//        for (LongTerms.Bucket bucket : buckets) {
//            long bid = bucket.getKeyAsNumber().longValue();
//            Brand brand = brandClient.queryBrandById(bid);
//            brandList.add(brand);
//        }
//        return brandList;
    }

    private List<Map<String, Object>> getCategoryAgg(Aggregation aggregation) {
        LongTerms longTerms = (LongTerms) aggregation;
        return longTerms.getBuckets().stream().map(x ->
                {
                    Map<String, Object> map = new HashMap<>();
                    long cid = x.getKeyAsNumber().longValue();
                    List<Category> categories = categoryClient.queryListByIds(Arrays.asList(cid));
                    map.put("id", cid);
                    map.put("name", categories.get(0).getName());
                    return map;
                }
        ).collect(Collectors.toList());
    }

    public void createOrUpdateIndex(Long spuId) {
        Spu spu = goodsClient.querySpuById(spuId);
        Goods goods = buildGoods(spu);
        goodsRepository.save(goods);
    }

    public void deleteIndex(Long spuId) {
        goodsRepository.deleteById(spuId);
    }
}
